﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace nMongo
{
    public class MongoQueryCollection<T> : Collection<T>
        where T : class
    {
        protected MongoEngine _engine;

        #region Constructors (4 overloads)
        public MongoQueryCollection()
            : this(null, null)
        { }

        public MongoQueryCollection(MongoEngine engine)
            : this(engine, null)
        { }

        public MongoQueryCollection(List<T> list)
            : this(null, list)
        { }

        public MongoQueryCollection(MongoEngine engine, List<T> list)
        {
            _engine = engine;
            if (list != null)
            {
                list.ForEach(x => this.Add(x));
            }
        }
        #endregion

        public MongoQueryCollection<T> Include<C>(string foreignKeyMember, string destinationMember)
            where C : class
        {
            using (_engine.Database.RequestStart())
            {
                for (int i = 0; i < this.Count; i++)
                {
                    FieldOrPropertyInfo foreignKeyInfo = typeof(T).GetFieldOrProperty(foreignKeyMember);
                    FieldOrPropertyInfo destinationInfo = typeof(T).GetFieldOrProperty(destinationMember);

                    object destinationValue;

                    if (foreignKeyInfo.MemberType.IsGenericType)
                    {
                        Type listType = typeof(ObservableCollection<>);
                        Type fieldListType = destinationInfo.MemberType.GenericTypeArguments.First();
                        Type constructedListType = listType.MakeGenericType(fieldListType);
                        IList instance = (IList)Activator.CreateInstance(constructedListType);

                        object foreignKey = foreignKeyInfo.IsField ? foreignKeyInfo.Field.GetValue(this[i]) : foreignKeyInfo.Property.GetValue(this[i]);
                        foreach (object related in (IList)foreignKey)
                        {
                            C returned = _engine.GetById<C>(related);
                            instance.Add(returned);
                        }
                        destinationValue = instance;
                    }
                    else
                    {
                        object foreignKey = foreignKeyInfo.IsField ? foreignKeyInfo.Field.GetValue(this[i]) : foreignKeyInfo.Property.GetValue(this[i]);
                        destinationValue = _engine.GetById<C>(foreignKey);
                    }

                    T obj = this[i];
                    if (destinationInfo.IsField)
                        destinationInfo.Field.SetValue(obj, destinationValue);
                    else
                        destinationInfo.Property.SetValue(obj, destinationValue);
                    this[i] = obj;
                }
                return this;
            }
        }

        public void SetEngine(MongoEngine engine)
        {
            _engine = engine;
        }
    }
}
